package com.twoscreen;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.NotPersistent;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import org.datanucleus.exceptions.NoPersistenceInformationException;
import org.json.JSONObject;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.twoscreen.test.Test;
import com.twoscreen.test.TestMethod;


/**
 * Class provides information on which users are connected to which player screen (PlayerScreenServlet)
 * as well as the permanent storage for a fixed period
 * @author James Smith
 */
@PersistenceCapable
public class DeviceConnect {
	@NotPersistent
	private static PersistenceManager pm = PMF.get().getPersistenceManager();
	
	@NotPersistent
	private static final Logger log = Logger.getLogger("com.twoscreen.DeviceConnect.java");
	
	@NotPersistent
	private static final String psIdPropertyName = "psID";
	
	@NotPersistent
	private static final String timestampPropertyName = "timeStamp";
	
	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	private Key key;
	
	@Persistent
	private String psID;
	
	@Persistent
	private Date timeStamp;
	
	public DeviceConnect(String viewerId, String psID) {
		//Columns: KEY: viewerId | psID | timestamp // key is viewerId 1:m psID
			//as this is not for logging and just for communication that is acceptable
		Key key = KeyFactory.createKey(DeviceConnect.class.getSimpleName(), viewerId);
		this.key = key;
		this.psID = psID;
		this.timeStamp = new Date();
		
		//Cache the information
			//Get any current cached info first
        ArrayList<String> array = (ArrayList<String>) DataCache.getArrayListFromCache(psID);
        	//Check the value is not already in the cache
        if (!array.contains(viewerId)) {
        	array.add(viewerId);
        	log.info("value added to cache array");
        } else {
        	log.info("value in cache array");
        }
        	//Add the screen connections array
        DataCache.addToCache(psID, array);
        log.info("Connection cached: psID: " + psID +
				" viewerId: " + viewerId);
        	//Add the viewer screen connection
        DataCache.addToCache(viewerId, psID);
		
		//Store in the DataStore
		pm.makePersistent(this);
		log.info("Connection added: id: " + viewerId +
				" psID: " + psID +
				" timeStamp: " + timeStamp);
	}

	public Key getKey() {
		return key;
	}

	public String getPsID() {
		return psID;
	}

	public Date getTimeStamp() {
		return timeStamp;
	}

	public static Logger getLog() {
		return log;
	}

	public static String getPsidpropertyname() {
		return psIdPropertyName;
	}

	public static String getTimestamppropertyname() {
		return timestampPropertyName;
	}

	/**
	 * Queries the DataStore to return a list of viewerId's currently watching the passed player screen ID
	 * @param psID The player screen ID for the query
	 * @return An ArrayList of Strings containing matched viewerId's
	 */
	@SuppressWarnings("unchecked")
	protected static ArrayList<String> getConnections(String psID) {
		log.info("Creating a list of connections. psID passed: " + psID);
		//Create the list to be returned
		//Try getting the matching connections from the cache first
		ArrayList<String> list = (ArrayList<String>) DataCache.getArrayListFromCache(psID);
		log.info("Cache empty: " + list.isEmpty());
		
		if (list.isEmpty()) {
			//Get the matching connections from the Data Store if no matches are found in the cache
			log.info("Start of Query");
			Query q = pm.newQuery(DeviceConnect.class);
			q.setFilter("psID == psIdParam");
			q.declareParameters("String psIdParam");
			
			log.info("Start of results try");
			try {
				List<DeviceConnect> results = (List<DeviceConnect>) q.execute(psID);
				log.info("Start of results loop");
				if (!results.isEmpty()) {
					for (DeviceConnect result : results) {
						String currentViewerId = result.getKey().getName();
						log.info("Adding viewerID: " + currentViewerId);
						list.add(currentViewerId);
					}
				} else {
					log.info("No connected devices found");
				}
			} finally {
				q.closeAll();
			}
		}
		
		log.info("List created");
		return list;
	}
	
	/**
	 * Returns a DeviceConnect object from the data store based on the passed viewerID
	 * @param viewerId The viewer ID to search for
	 * @return A DeviceConnect object
	 */
	protected static DeviceConnect getDC(String viewerId) {
		DeviceConnect dc = null;
		try {
			//found solution here: http://db.apache.org/jdo/api20/apidocs/javax/jdo/PersistenceManager.html
			Key key = KeyFactory.createKey(DeviceConnect.class.getSimpleName(), viewerId);
			log.info("key: " + key);
			dc = (DeviceConnect) pm.getObjectById(DeviceConnect.class, key);
		} catch (NullPointerException e) {
			log.info("Test failed on Viewer ID: " + viewerId + e);
		} catch (JDOObjectNotFoundException e) {
			log.info("Test failed on Viewer ID: " + viewerId + e);
		} catch (NoPersistenceInformationException e) {
			log.warning("No data in DataStore Viewer ID: " + viewerId + e);
		}
		return dc;
	}
	
	/**
	 * Runs all the tests on the class
	 * @return Test results JSON
	 */
	public static JSONObject test() {
		Test test = new Test("DeviceConnect");
		test.addMethod(testConstructor());
		test.addMethod(testGetConnections());
		test.addMethod(testGetDC());
		return test.getJSONObject();
	}
	
	/**
	 * Tests the constructor method
	 * @return Test results JSON
	 */
	private static JSONObject testConstructor() {
		TestMethod tm = new TestMethod("testConstructor");
		
		//Test base
		String viewerId = "abcd";
		String psID = "efgh";
		DeviceConnect test = new DeviceConnect(viewerId, psID);
    	
		//entryExists Test
		Boolean test1 = false;
    	String testStack1 = "";
    	try {
    		DeviceConnect inTest = pm.getObjectById(DeviceConnect.class, viewerId);
    		test1 = (inTest != null);
    	} catch (Exception e) {
    		testStack1 = e.getMessage();
    	}
		tm.addTest("Entry exists", test1, testStack1);
		
		//viewerIdCorrect Test
		Boolean test2 = false;
    	String testStack2 = "";
    	try {
    		DeviceConnect inTest = pm.getObjectById(DeviceConnect.class, viewerId);
    		test2 = (inTest.getKey().getName().equals(viewerId));
    	} catch (Exception e) {
    		testStack2 = e.getMessage();
    	}
		tm.addTest("Viewer ID correct", test2, testStack2);
		
		//Remove the test object
		pm.deletePersistent(test);
		
    	return tm.getJSONObject();
    }
	
	/**
	 * Tests the getConnections method
	 * @return Test results JSON
	 */
	private static JSONObject testGetConnections() {
		TestMethod tm = new TestMethod("testGetConnections");
		
		//Test base
		String psID = "efgh";
		DeviceConnect testItem1 = new DeviceConnect("abcd", psID);
		DeviceConnect testItem2 = new DeviceConnect("ijkl", psID);
		DeviceConnect testItem3 = new DeviceConnect("mnop", psID);
		
		//lastEntryExists Test
		Boolean test1 = false;
    	String testStack1 = "";
    	try {
    		DeviceConnect inTest = pm.getObjectById(DeviceConnect.class, "mnop");
    		test1 = (inTest != null);
    	} catch (Exception e) {
    		testStack1 = e.getMessage();
    	}
		tm.addTest("Last entry exists", test1, testStack1);
		
		//containsThreeItems Test
		ArrayList<String> test = getConnections(psID);
		Boolean test2 = false;
    	String testStack2 = "";
    	try {
    		test2 = (test.size() == 3);
    		testStack2 += "Number of items: " + test.size() + "<br>";
    		testStack2 += "First item: " + test.get(0) + "<br>";
    	} catch (Exception e) {
    		testStack2 += e.getMessage();
    	}
		tm.addTest("Contains three items", test2, testStack2);
		
		//Remove the test object
		pm.deletePersistent(testItem1);
		pm.deletePersistent(testItem2);
		pm.deletePersistent(testItem3);
		
    	return tm.getJSONObject();
    }
	
	/**
	 * Tests the getDC method
	 * @return Test results JSON
	 */
	private static JSONObject testGetDC() {
		TestMethod tm = new TestMethod("testGetDC");
		
		//Test base
		String psID = "efgh";
		DeviceConnect testItem1 = new DeviceConnect("abcd", psID);
		DeviceConnect testItem2 = new DeviceConnect("ijkl", psID);
		DeviceConnect tester1 = getDC("abcd");
		DeviceConnect tester2 = getDC("ijkl");
		
		//getItemOne Test
		Boolean test1 = false;
    	String testStack1 = "";
    	try {
    		test1 = (tester1.getKey().getName().equals("abcd"));
    	} catch (Exception e) {
    		testStack1 = e.getMessage();
    	}
		tm.addTest("Get item one", test1, testStack1);
		
		//getItemTwo Test
		Boolean test2 = false;
    	String testStack2 = "";
    	try {
    		test2 = (tester2.getKey().getName().equals("ijkl"));
    	} catch (Exception e) {
    		testStack2 = e.getMessage();
    	}
		tm.addTest("Get item two", test2, testStack2);
		
		//Remove the test object
		pm.deletePersistent(testItem1);
		pm.deletePersistent(testItem2);
		
    	return tm.getJSONObject();
    }


}
